package eteam.aps.gateway.filter;

import cn.hutool.core.util.ArrayUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONUtil;
import com.fasterxml.jackson.databind.ObjectMapper;
import eteam.aps.common.R;
import eteam.aps.common.exceptions.AuthException;
import eteam.aps.common.helper.Helper;
import eteam.aps.common.helper.JwtUtils;
import eteam.aps.gateway.client.SystemAuthFeignClient;
import eteam.aps.gateway.client.SystemFeignClient;
import eteam.aps.gateway.client.SystemLogFeignClient;
import eteam.aps.gateway.config.ModuleProperty;
import eteam.aps.systeminterface.model.dto.LogDto;
import eteam.aps.systeminterface.model.dto.ModuleoperDto;
import eteam.aps.systeminterface.model.dto.VerifyTokenDto;
import org.apache.commons.lang.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

import java.net.URLEncoder;
import java.util.*;

/**
 * 鉴权过滤器
 */
@Component
public class AuthFilter implements GlobalFilter, Ordered {
    private final static String TOKEN = "token";
    
    @Autowired
    private ModuleProperty moduleProperty;
    @Autowired
    private SystemAuthFeignClient authFeignClient;
    @Autowired
    private SystemFeignClient systemFeignClient;
    @Autowired
    private SystemLogFeignClient logFeignClient;

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        //return chain.filter(exchange);

        // 获取请求
        ServerHttpRequest request = exchange.getRequest();
        // 获取响应
        ServerHttpResponse response = exchange.getResponse();
        try {
            String requestPath = request.getPath().value();

            //#region 忽略文档URL
            if (StringUtils.endsWithIgnoreCase(requestPath, "/v2/api-docs")) {
                return chain.filter(exchange);
            }
            //#endregion

            //#region 判断是否为忽略URL
            Object[] urlArr = moduleProperty.getExcludeUrls().toArray();
            if (ArrayUtil.isNotEmpty(urlArr) && ArrayUtil.contains(urlArr, requestPath)) {
                return chain.filter(exchange);
            }
            //#endregion

            //#region 获取token
            HttpHeaders headers = request.getHeaders();
            String token = headers.getFirst(TOKEN);
            // 如果请求头中的令牌为空, 则返回错误状态码
            if (StringUtils.isEmpty(token)) {
                throw new Exception("Token无效");
                //throw new AuthException("Token无效");
            }
            //#endregion

            //#region 校验Token信息
            String jsonStr = JwtUtils.verifyTokenRetJson(token);
            VerifyTokenDto tokenDto = JSONUtil.toBean(jsonStr, VerifyTokenDto.class);
            if (Helper.checkObjAllFieldsIsNull(tokenDto)) {
                throw new AuthException("Token无效");
            }
            //#endregion

            //#region 调用接口校验Token信息
            R<VerifyTokenDto> verifyRet = authFeignClient.verifyToken(token);
            if (Objects.isNull(verifyRet)) {
                throw new Exception("Token无效");
            }
            if (verifyRet.getCode() != 200) {
                throw new Exception(verifyRet.getMsg());
            }
            tokenDto = verifyRet.getData();
            //#endregion

            //#region 根据RequestPath匹配数据库配置，得到操作对象
            ModuleoperDto operObj = null;
            try {
            	List<ModuleoperDto> operList = new ArrayList<>();
                if (ArrayUtil.isEmpty(operList.toArray())) {
                    R<List<ModuleoperDto>> operRet = systemFeignClient.getModuleopers();
                    if (!Objects.isNull(operRet) && operRet.getCode() == 200 && !operRet.getData().isEmpty()) {
                        operList.addAll(operRet.getData());
                    }
                }
                Optional<ModuleoperDto> tmpObj = operList.stream().filter(x -> requestPath.endsWith(x.getPath())).findAny();
                if (tmpObj.isPresent()) {
                    operObj = tmpObj.get();
                }
            } catch (Exception ex) {
                ex.printStackTrace();
            }
            //#endregion

            //#region 记录日志/功能校验
            if (!Objects.isNull(operObj)) {
                // 功能校验
                if (operObj.getVerifyuc()) {
                    Map<String, String> mapObj = verifyUc(token, operObj);
                    if (!mapObj.get("flag").equals("true")) {
                        throw new Exception("无权限访问此功能");
                    }
                }
                // 记录日志
                if (operObj.getWritelog()) {
                    writeLog(token, tokenDto.getFfullname(), operObj);
                }
            }
            //#endregion

            //#region 解析正常则放行

            //String str = gson.toJson(result);
            //byte[] data = str.getBytes(StandardCharsets.UTF_8);
            //response.getHeaders().setContentType(request.getHeaders().getContentType());
            //response.getHeaders().setContentType(MediaType.APPLICATION_JSON);
            //return writeToResponse(response, data);

            ServerHttpRequest newRequest = request.mutate().header("tokenInfo", URLEncoder.encode(JSONUtil.toJsonStr(tokenDto), "UTF-8")).build();
            ServerWebExchange newExchange = exchange.mutate().request(newRequest).build();
            newExchange.getAttributes().put("tokenInfo", tokenDto);
            return chain.filter(newExchange);
            //return chain.filter(exchange);

            //#endregion
        } catch (Exception ex) {
            response.setStatusCode(HttpStatus.UNAUTHORIZED);
            try {
                //response.getHeaders().setContentType(request.getHeaders().getContentType());
                //response.getHeaders().setContentType(MediaType.APPLICATION_JSON);
                response.getHeaders().add("Content-Type", "application/json;charset=UTF-8");
                // 构建返回对象
                R<Object> responseData = R.failed(HttpStatus.UNAUTHORIZED.value(), ex.getMessage());
                // 将信息转换为 JSON
                ObjectMapper objectMapper = new ObjectMapper();
                //byte[] data = objectMapper.writeValueAsString();
                byte[] data = objectMapper.writeValueAsBytes(responseData);
                // 输出错误信息到页面
                DataBuffer buffer = response.bufferFactory().wrap(data);
                return response.writeWith(Mono.just(buffer));
            } catch (Exception e) {
                e.printStackTrace();
                return response.setComplete();
            }
        }
    }

    @Override
    public int getOrder() {
        return 1;
        //return Ordered.LOWEST_PRECEDENCE;
    }

    /**
     * 校验功能权限
     *
     * @param token
     * @param operObj
     * @return
     */
    private Map<String, String> verifyUc(String token, ModuleoperDto operObj) {
        Map<String, String> retMap = new HashMap<>();
        retMap.put("flag", "false");
        retMap.put("msg", "无权限操作此功能");
        try {
            R<List<String>> ucRet = authFeignClient.getUcList(token);
            if (Objects.isNull(ucRet) || ucRet.getCode() != 200 || ucRet.getData().isEmpty()) {
                throw new Exception(Objects.isNull(ucRet) ? "获取权限异常" : ucRet.getMsg());
            }
            if (ucRet.getData().stream().filter(x -> x.equals(operObj.getModuleoper())).count() < 1) {
                throw new Exception("无权限操作此功能");
            }
            retMap.put("flag", "true");
        } catch (Exception ex) {
            ex.printStackTrace();
            retMap.put("msg", ex.getMessage());
        }
        return retMap;
    }

    /**
     * 写操作日志
     *
     * @param token
     * @param userName
     * @param operObj
     */
    private void writeLog(String token, String userName, ModuleoperDto operObj) {
        try {
            LogDto tmpLog = new LogDto();
            tmpLog.setFmoduleid(operObj.getModuleid());
            tmpLog.setFmodulename(operObj.getModulename());
            tmpLog.setFoper(operObj.getModuleoper());
            tmpLog.setFopername(operObj.getModuleopername());
            tmpLog.setFdesc(StrUtil.format("【{}】操作了【{}】-【{}】", userName, operObj.getModulename(), operObj.getModuleopername()));
            R<Object> logRet = logFeignClient.createLog(token, tmpLog);
            if (Objects.isNull(logRet) || logRet.getCode() != 200) {
                throw new Exception(Objects.isNull(logRet) ? "记录日志异常" : logRet.getMsg());
            }
        } catch (Exception ex) {
            ex.printStackTrace();
        }
    }
}